package cn.jingyuan.swan.cloud.web;


import cn.jingyuan.bee.exception.SystemException;
import cn.jingyuan.bee.fastjson.FastJsonUtils;
import cn.jingyuan.swan.cloud.core.ExCode;
import cn.jingyuan.swan.cloud.core.RestResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * 抽象异常处理器
 */
public abstract class AbstractExceptionHandler {

    protected  Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 解析异常
     *
     * @param ex 异常
     */
    public static <D>  RestResult<D> resolveEx(Exception ex) {
        RestResult<D> result = resolveExSystem(ex);

        if (Objects.nonNull(result)) {
            return result;
        }

        result = resolveExWeb(ex);
        if (Objects.nonNull(result)) {
            return result;
        }

        ExCode exCode = ExCode.INTERNAL_SERVER_ERROR;
        Integer httpStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();

        return new RestResult<D>()
            .setCode(exCode.getCode())
            .setCodeMsg(exCode.getCodeMsg())
            .setHttpStatus(httpStatus);
    }

    /**
     * 解析 SystemException 及其子类异常
     * <pre>
     *     1、request or response 可以为 {@code null}
     * </pre>
     *
     * @param ex {@link Exception} 系统异常
     *
     * @return {@code null} 如果 ex is not instanceof SystemException 为 {@code null}
     */
    public static <D> RestResult resolveExSystem(Exception ex) {
        if (!(ex instanceof SystemException)) {
            return null;
        }

        SystemException sysEx = (SystemException) ex;

        String code = sysEx.getCode();
        String codeMsg = sysEx.getCodeMsg();

        HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

        // 1005 为拒绝访问异常 --> ExCode.ACCESS_XXX
        // FIXME
        if (code.startsWith("1005")) {
            httpStatus = HttpStatus.FORBIDDEN;
        }


        return new RestResult<D>()
            .setCode(code)
            .setCodeMsg(codeMsg)
            .setHttpStatus(httpStatus.value());
    }

    public static <D> RestResult<D> resolveEx(Exception ex, HttpServletRequest request, HttpServletResponse response) {
        RestResult<D> result = resolveEx(ex);

        response.setStatus(result.getHttpStatus());

        return result;
    }

    private static <D> RestResult buildResult(ExCode exCode, HttpStatus status) {
        return new RestResult<D>()
            .setCode(exCode.getCode())
            .setCodeMsg(exCode.getCodeMsg())
            .setHttpStatus(status.value());
    }

    /**
     * 解析  Web（Servlet 及 Mcv） 异常
     * <pre>
     *     1、request or response 可以为 {@code null}
     * </pre>
     *
     * @param ex Servlet 及 Mcv 异常
     *
     * @return {@code null} 如果 ex 为 {@code null}
     */
    public static <D> RestResult<D> resolveExWeb(Exception ex) {
        if (Objects.isNull(ex)) {
            return null;
        }

        // ex 400
        if (ex instanceof ServletRequestBindingException ||
            // ex instanceof TypeMismatchException ||
            ex instanceof HttpMessageNotReadableException ||
            ex instanceof MethodArgumentNotValidException ||
            ex instanceof MissingServletRequestPartException ||
            ex instanceof BindException
        ) {
            return buildResult(ExCode.BAD_REQUEST, HttpStatus.BAD_REQUEST);
        }

        // ex 404
        if (ex instanceof NoHandlerFoundException) {
            return buildResult(ExCode.NOT_FOUND, HttpStatus.NOT_FOUND);
        }

        // ex 405
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return buildResult(ExCode.METHOD_NOT_ALLOWED, HttpStatus.METHOD_NOT_ALLOWED);
        }

        // ex 406
        if (ex instanceof HttpMediaTypeNotAcceptableException) {
            return buildResult(ExCode.NOT_ACCEPTABLE, HttpStatus.NOT_ACCEPTABLE);
        }

        // ex 415
        if (ex instanceof HttpMediaTypeNotSupportedException) {
            return buildResult(ExCode.UNSUPPORTED_MEDIA_TYPE, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
        }

        // ex 500
        if (ex instanceof ConversionNotSupportedException ||
            ex instanceof HttpMessageNotWritableException ||
            ex instanceof MissingPathVariableException
        ) {
            return buildResult(ExCode.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR);
        }

        // ex 503
        if (ex instanceof AsyncRequestTimeoutException) {
            return buildResult(ExCode.INTERNAL_SERVER_ERROR, HttpStatus.SERVICE_UNAVAILABLE);
        }

        return null;
    }

    @ExceptionHandler({Exception.class})
    public <D> RestResult<D> handler(Exception ex, HttpServletRequest request, HttpServletResponse response) {
        logException(ex);
        RestResult<D> result = resolveEx(ex, request, response);

        // FIXME 待修正
        result.setMessage(result.getCodeMsg());

        response.setStatus(result.getHttpStatus());

        return result;
    }

    /**
     * 打印异常信息
     *
     * @param ex 异常
     */
    public void logException(Exception ex) {
        String exName = ex.getClass().getName();
        if (ex instanceof SystemException) {
            SystemException sysEx = (SystemException) ex;
            String paramsValue = null;

            try {
                paramsValue = FastJsonUtils.json(sysEx.getParams());
            } catch (Exception e) {
                // no thing to do
            }

            log.error("exName:{} , code={} , codeMsg={} , params={} , detailMessage={}",
                exName,
                sysEx.getCode(),
                sysEx.getCodeMsg(),
                paramsValue,
                sysEx.getLocalizedMessage(),
                ex);
        } else {
            log.error("exName:{} , detailMessage={}", exName, ex.getLocalizedMessage(), ex);
        }
    }

}
